#ifndef _KLAS_MATRIX_4_HPP_
#define _KLAS_MATRIX_4_HPP_

#include "../Platform.hpp"
#include "../math/Vector2.hpp"
namespace klas
{
  namespace math
  {
    class Matrix4
    {
      public:

      union
      {
          double data[16];

          struct
          {
              double m00; double m01; double m02; double m03;
              double m10; double m11; double m12; double m13;
              double m20; double m21; double m22; double m23;
              double m30; double m31; double m32; double m33;
          };
      };


      /**
       * Default constructor
       * Makes an idenity matrix
       */
      Matrix4();

      /**
       * constructor that take all values of the matrix
       */
      Matrix4(double m00, double m01, double m02, double m03,
              double m10, double m11, double m12, double m13,
              double m20, double m21, double m22, double m23,
              double m30, double m31, double m32, double m33);

      /**
       * Constructor that takes another matrix
       * @param m the matrix to clone
       */
      Matrix4(const Matrix4 &m);

      /**
       * Default destructor
       */
      ~Matrix4();

      /**
       * Turns the matrix into an idenity matrix
       */
      void identity();


      /**
       * Transposes the matrix
       */
      void transpose();


      /**
       * @return the transposed version of the matrix
       */
      Matrix4 transposed() const;


      /**
       * Makes a new Matrix rotated around the X axis
       * @param degrees the degrees to rotate
       * @return a matrix rotated around the X axis
       */
      Matrix4 static rotateX( const double degrees );


      /**
       * Makes a new Matrix rotated around the Y axis
       * @param degrees the degrees to rotate
       * @return a matrix rotated around the Y axis
       */
      Matrix4 static rotateY( const double degrees );


      /**
       * Makes a new Matrix rotated around the Z axis
       * @param degrees the degrees to rotate
       * @return a matrix rotated around the Z axis
       */
      Matrix4 static rotateZ( const double degrees );


      /**
       * Makes a new scaled Matrix
       * @param x the amount to scale on the X axis
       * @param y the amount to scale on the Y axis
       * @param z the amount to scale on the Z axis
       * @return a scaled matrix
       */
      Matrix4 static scale( const double x, const double y , const double z );


      /**
       * Makes a new scaled Matrix
       * @param v a vector with the scale amounts
       * @return a scaled matrix
       */
      Matrix4 static scale( const Vector2d &v);


      /**
       * Get's the scales for all axis
       * @param v a vector scales to populate
       */
      Vector2d getScale() const;


      /**
       * Makes a translation matrix
       * @param x the X axis translation
       * @param y the Y axis translation
       * @return a translation matrix
       */
      Matrix4 static translate(double x, double y);


      /**
       * Makes a translation matrix
       * @param v the vector to translate with
       * @return a translation matrix
       */
      Matrix4 static translate(const Vector2d& v );


      /**
       * Get's the translations for all axis
       * @return A Vector2d representing the translation
       */
      Vector2d getTranslation() const;

      /**
       * Helper function to get part of matrix
       * @param i x-column to ignore
       * @param j y-column to ignore
       * @param mat the output matrix
       */
      void subMatrix3x3(int i, int j, double* mat) const;


      /**
       * A fast method for inversing a matrix, it only works on affine matrices (translations and rotations only)
       * @param result the result of the inverse
       */
      void fastInverse(Matrix4* result);

      /**
       * A slower method for inversing a matrix, works on all matrices
       * @param result the result of the inverse
       * @return if the matrix could be inversed
       */
      bool inverse(Matrix4* result) const;

      /**
       * Inverses the matrix
       * @return if the matrix could be inversed
       */
      bool inverse();

      /**
       * @return the determinant of the matrix
       */
      double determinant() const;


      /**
       * Compares two matrices for equality
       * @param the matrix to compare to
       * @return if the matrices are equal
       */
      const bool operator==(const Matrix4 &m) const;


      /**
       * Compares two matrices for inequality
       * @param the matrix to compare to
       * @return if the matrices are not equal
       */
      const bool operator!=( const Matrix4 &m);


      /**
       * assignment operator
       * @param rhs matrix values to assign with
       * @return a new matrix with the value of rhs
       */
      const Matrix4& operator=(const Matrix4 &rhs);


      /**
       * multiplication equals operator against a double, multiples it by all elements in a matrix
       * @param f double value to multiply with
       * @return a new multiplied matrix
       */
      const Matrix4& operator*=( const double &f);


      /**
       * divide equals operator against a double, divides it by all elements in a matrix
       * @param f double value to divide with
       * @return a new divided matrix
       */
      const Matrix4& operator /= ( const double &f);


      /**
       * multiplication equals operator against a matrix, applies a matrix transformations
       * @param m matrix to multiply with
       * @return a new transformed matrix
       */
      const Matrix4& operator *= ( const Matrix4 &m);

      /**
       * multiplication operator against a matrix, applies a matrix transformations
       * @param m matrix to multiply with
       * @return a new transformed matrix
       */
      Matrix4  operator * ( const Matrix4 &m) const;

      /**
       * Sets the matrix as the current opengl matrix
       */
      void apply();
    };

  }
}

#endif